import axios from 'axios';
import QS from 'qs';
import CryptoJS from 'crypto-js'
import useHttpStore from '@/stores/httpStore' //引入http仓库
//提供简单 API 将数据（如 Blob、字符串）直接保存为本地文件，自动处理浏览器兼容性和下载链接创建逻辑
import fileDownload from 'js-file-download';



const HttpCode = { //http状态码对象
  http400: '400错误，请求报文中有语法错误',
  http401: '401错误，用户未授权',
  http403: '403错误，服务器拒绝该请求',
  http404: '404错误，请求不存在',
  http405: '405错误，请求方法被禁用',
  http408: '408错误，会话超时',
  http415: '415错误，不支持的媒体类型',
  http500: '500错误，服务器报错',
  http502: '502错误，网关错误',
  http503: '503错误，服务未启动',
  http504: '504错误，网关超时',
  http506: '数据库连接异常，操作失败',
  http1705: '超时未操作，请重新登录',
}
/**需要使用到的方法 start */

/**
 * 将对象转formData对象
 * @param obj 对象
 * @returns {FormData}
 */
const object2FormData = (obj = {}) => {
  const fd = new FormData();
  Object.keys(obj).forEach((key) => {
    const val = obj[key];
    if (val || val === 0) {
      if (Array.isArray(val)) {
        val.forEach((r, i) => {
          if (key === 'multipartFileList' && (r && typeof r === 'object')) {
            fd.append(key, r);
          } else {
            for (let rkey in r) {
              fd.append(`${key}[${i}].${rkey}`, r[rkey]);
            }
          }
        });
      } else {
        fd.append(key, val);
      }
    }
  });

  return fd;
};

/**
* 生成随机数和时间戳，提供给某些接口，用来避免报文重放问题
*/
const generateSecureRandomNumber = (min = 1, max = 100000) => {
  let str = '';
  let randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;//随机生产范围随机数
  let date = new Date().getTime();//根据当前时间生成时间戳
  str = `${date}_${randomNumber}`;//拼接成字符串

  if (!str) return '';
  let keyStr = 'JIAMIMIYAO';
  let source = CryptoJS.enc.Utf8.parse(str);
  let encrypted = CryptoJS.AES.encrypt(
    source,
    CryptoJS.enc.Utf8.parse(keyStr),
    {
      mode: CryptoJS.mode.ECB,
      padding: CryptoJS.pad.Pkcs7,
    },
  );
  let hexValue = encrypted.ciphertext.toString();
  let parseHex = CryptoJS.enc.Hex.parse(hexValue);
  let RandomText = CryptoJS.enc.Base64.stringify(parseHex);
  return RandomText;
};


const showMsg = (result, data) => {
  if (result === 'success') {
    //请求成功，根据data返回不同的值
    if (data?.code !== 200 && data.message) {
      console.log('请求成功，但操作失败，失败原因为', data.message)
    }
  } else {
    //请求失败，根据状态码信息输出不同的值
    Object.keys(HttpCode).includes(data) && console.log('请求失败,失败原因为', HttpCode[data])

  }
}
/**需要使用到的方法 end */


const productionConfig = window.productionConfig //全局配置
window.axiosCancel = []; //全局定义一个存放取消请求的数组

/** 修改axios默认配置 start */

//禁止缓存,兼容性;置为 no-cache 时，Pragma 请求头会告诉服务器和浏览器不要使用缓存的响应，而是每次都从服务器重新获取数据
// 用于兼容 HTTP/1.0 的缓存控制机制
axios.defaults.headers.common.Pragma = 'no-cache' //不要使用缓存的响应，而是每次都从服务器重新获取数据
axios.defaults.headers.common['Cache-Control'] = 'no-cache';  //兼容 HTTP/1.0 的缓存控制机制


//设置axios的基础路径，读取配置文件
const prodBaseUrl = `${productionConfig.protocol}//${productionConfig.hostname}:${productionConfig.protocol === 'http' ? productionConfig.httpPort : productionConfig.httpsPort}${productionConfig.productType === 1 ? '/api' : '/dsc'}`

//根据不同的环境设置不同的baseURL
axios.defaults.baseURL = process.env.NODE_ENV === 'development' ? '/api' : prodBaseUrl; //设置axios的基础路径，读取配置文件
//设置请求超时时间
axios.defaults.timeout = 1000 * 6; //设置请求超时时间，单位为毫秒

// transformRequest 允许在向服务器发送前，修改请求数据。只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
//这里用来修改携带的参数格式，使用的是qs库，其他库也可以
axios.defaults.transformRequest = [function (data, headers) {
  //如果没有设置请求头数据，直接返回一个用&连接的序列化结果
  if (!headers['Content-Type']) return QS.stringify(data); //使用qs库将data转换为字符串格式
  //如果设置了请求头数据，根据数据类型返回不同的序列化结果
  switch (headers['Content-Type']) {
    case 'application/json;charset=utf-8': {//如果请求头数据类型为application/json;charset=utf-8
      return JSON.stringify(data); //转化成json格式的字符串返回
    }
    case 'multipart/form-data;charset=utf-8': {//如果设置的是表单数据格式，直接返回data
      return data;
    }
    default: {//如果设置的是其他格式，使用qs库将data转换为字符串格式用&进行拼接
      return QS.stringify(data);
    }
  }
}];

/** 修改axios默认配置 end */



/** 请求拦截器 start */
axios.interceptors.request.use(
  (config) => {//config是请求的配置对象
    const httpStore = useHttpStore(); //创建http仓库实例
    //增加请求数量
    httpStore.addRequestSum()
    //增加等待请求数量
    httpStore.addPendingNum()
    //设置为正在请求的状态
    httpStore.setIsPedding(true)
    //设置token
    config.headers['Authorization'] = localStorage.getItem('token') || ''; //设置请求头中的Authorization字段为token
    //保证取消请求的数组长度不大于300,如果大于300,则删除前100个
    window.axiosCancel.length > 300 && window.axiosCancel.splice(0, 100);
    //通过CancelToken()构造函数创建可取消的请求令牌，用uri标识生成规则
    config.cancelToken = new axios.CancelToken((cancel) => {
      window.axiosCancel.push(
        {
          uri: `${config.url}&${config.method}`,//请求的url和请求的方法作为唯一标识
          cancelFn: cancel //取消请求的函数
        }
      );
    });
    return config; //返回请求的配置对象
  }, (error) => {//error是请求的错误对象
    console.log('请求拦截器错误', error)
    Promise.reject(error) //返回一个拒绝的Promise对象
  }
)

/** 请求拦截器 end */


/** 响应拦截器 start */
axios.interceptors.response.use(
  (res) => {//res为响应的对象，仅处理2xx的成功响应\
    const httpStore = useHttpStore(); //创建http仓库实例
    //减少等待请求数量
    httpStore.reducePendingNum()
    //设置为不是正在请求的状态
    httpStore.setIsPedding(false)
    //请求成功，根据消息展示消息
    showMsg('success', res.data)
    return res; //返回响应的对象
  }, (error) => {//错误的回调，处理非2xx的回调
    const httpStore = useHttpStore(); //创建http仓库实例
    httpStore.reduceRequestSum()
    httpStore.setIsPedding(false)
    let codeNumber = error.toString().split(' ').pop(); //获取失败状态码
    let reLoginCodes = ['403', '408', '502', '504', '1705'];// 如果返回以下状态码，需要重新登录,做其他操作
    showMsg('fail', `http${codeNumber}`)
    return { //返回响应code为-1
      code: -1,
    }
  }
)

/** 响应拦截器 end */

/**
 * 通过用户传入的method方法，query参数来决定发送请求时需要装载的配置内容
 */

async function requestFn(require) {//传入一个请求(普通对象)进来 {method,url,query,success,error,userOption}

  /**
   * 一堆flag的定义,后面会根据flag决定Content-Type用哪一种
   */

  //请求参数中是否有引用数据类型数据
  let hadReferenceType = false
  // 是否有文件类型数据 
  let hadFileType = false
  // blob类型接收大数据  表示二进制数据的对象，通常用于处理文件、图片等二进制内容。它可以存储大量二进制数据，并支持分片处理和文件操作
  let hadBlobBigData = false


  /**
   * 工具变量
   */

  //定义获取数据类型的方法  经典「万能类型检测」
  let toString = Object.prototype.toString
  //待会儿要计算的请求头
  let headers = {}
  //最终给 axios 的配置
  let adaptOption = {}
  //axios 返回的完整响应
  let response = {}


  //对get请求的[数组]特殊保护,作者怕你把数组直接放 GET 的 query 里，因为后端没做解析。
  if (require.method === 'get' && require.query && (toString.call(require.query) === '[object Array]')) {
    //仅做提示,不阻断
    console.error('在get请求下,后台还不知道接收复杂数据类型的方法---数组')
  }
  //遍历query 打标记
  //如果传入的query参数是数组，则将hadReferenceType设置为true，当delete，put等方法可能直接传入一个数组进来
  (toString.call(require.query) === '[object Array]') && (hadReferenceType = true)

  //如果传递进来的参数是对象类型，则进去查看对象中是否存在引用，文件，blob等类型数据
  if (toString.call(require.query) === '[object Object]') {
    Object.keys(require.query).forEach((item) => {
      switch (toString.call(require.query[item])) {
        case '[object Object]':
        case '[object Array]': {
          hadReferenceType = true;
          break;
        }
        case '[object File]': {
          hadFileType = true;
          break;
        }
        case '[object Blob]': {
          hadBlobBigData = true;
          break;
        }
        default: { break; }
      }
    });
  }

  // 为不同的数据类型适配不同的请求头：1.有文件数据；2、有引用数据类型(分为数组或是对象)，3、其他的key：value形式的存储
  headers = hadFileType ? { 'Content-Type': 'multipart/form-data;charset=utf-8' }
    : hadReferenceType ? { 'Content-Type': 'application/json;charset=utf-8' }
      : hadBlobBigData ? headers : { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' };

  //装载发送请求时的配置
  adaptOption = {
    method: require.method, //require传入的method方法
    url: require.url, // require传入的url
    headers, //根据不同的数据类型适配不同的请求头
    data: require.method !== 'get' ? require.query : null, //如果是get请求则没有请求体，如果不是get请求，则将请求体设置为query
    params: require.method !== 'get' ? null : require.query, //如果是get请求则将参数设置为params
  };

  //查看是否有自定义配置，如果有自定义配置则进行替换
  if (require.userOption) {
    Object.keys(require.userOption).forEach((key) => {
      if (Object.keys(adaptOption).includes(key)) {
        //浅拷贝,会合并对象的属性
        Object.assign(adaptOption[key], require.userOption[key])//源对象（userOption）的属性会覆盖目标对象（adaptOption）的属性
      } else {
        adaptOption[key] = require.userOption[key] //新增的配置项
      }
    });
  }

  //真正发送请求的地方,通过 async和await来进行异步控制请求的发送
  console.log("请求相关信息", adaptOption)
  response = await axios(adaptOption); //将adaptOption作为axios请求的配置，将axios(adaptOption)的返回值赋值给response
  console.log("响应相关信息", response)

  /**
   * 到此为止,网路请求已经发出去了,后面就是对响应的处理了
   */

  //如果后台返回的是一个文件流，则axios的response.data里面的将会是Blob对象的实例，之后就可以下载文件
  // 如果返回的不是文件流则说明是一般对象，就可以判断其中的code码
  if (response.data instanceof Blob) {
    //走文件流分支

    //拿到响应头的constent-disposition属性,获取文件名信息
    //Content-Disposition: attachment; filename="report.xlsx"
    //Content-Disposition: attachment; filename*=utf-8''%E6%8A%A5%E5%91%8A.xlsx
    let disposition = response.headers['content-disposition'];
    if (!disposition) {
      //如果没有disposition,可能是Blob二进制流,也可能后台返回的是JSON错误

      const fileReader = new FileReader() //FileReader用途 ：将Blob对象转换为文本，用于读取错误信息
      if (hadBlobBigData && (headers['Parma'] === 'bigData' || headers['parma'] === 'bigData')) {
        //发送请求时让后端将返回的文件流或者json错误信息都包装成Blog对象给前端接受,进入这里拿到的一定是Blob对象,但是错误信息也被包装成Blob对象,需要判断
        return new Promise((resolve, reject) => { //这里只处理错误json包装的Blob对象
          //文件读取完成后的回调函数
          fileReader.onload = function (e) {
            const result = fileReader.result; //读取文件内容的结果
            const json = JSON.parse(result); //将json结果转换为对象,如果不是JSON,也就是说结果是一个文件流,就会执行onerror
            require.success && require.success(json.data, response) //如果请求参数传入了success，那就将文件结果和respone传给success回调函数并执行
            resolve(json) //将结果返回
          },
            fileReader.onerror = function (e) {
              console.log("文件下载失败")
              reject(e);
            }

          //接收 Blob 或 File 类型的二进制数据作为参数,将二进制数据按指定编码（默认为UTF-8）解码为字符串
          //读取response.data,成功执行onload回调函数,失败执行 onerror回调函数
          fileReader.readAsText(response.data);
        })
      }
      else {
        fileReader.onload = function (e) {
          const result = fileReader.result; //拿到读取文件的结果
          const json = JSON.parse(result); //将json转换成Obj对象
          const content = json.message //获取message
          console.log(content)
        },
          fileReader.onerror = function (e) {
            console.log('文件下载失败')
          },
          fileReader.readAsText(response.data);
        require.error && require.error(response.data, response);//如果传递了失败的回调,则调用失败回调函数
        return false;
      }
    } else {
      //Blob类型并且有disposition,处理文件名之后直接进行下载
      disposition = disposition.replace("*=utf-8''", '=') //替换文件名
      let fileName = decodeURI(disposition.split('filename=')[1]); //获取文件名
      fileDownload(response.data, fileName) //下载文件
      //执行成功函数
      require.success && require.success(response.data.data, response)
    }
  }
  else {
    //走普通JSON分支,注意:axios 默认会把后端返回的 JSON 字符串 自动解析成 JavaScript 对象

    // 根据后端返回的状态码进行判断执行成功函数或者失败函数,注意两个函数返回数据的层级不一样,并且都可利用第二个参数来获取整个请求的内容
    response.data.code === 200
      ? (require.success && require.success(response.data.data, response))
      : (require.error && require.error(response.data, response));
  }
}


/*
* 注意method,url都是必填项,其他项都是非必填项.
* 请求成功和请求失败的情况下返回的数据层级不一致,为满足特殊需求,会传返回第二个参数(整个请求响应的内容). 编写成功或者错误函数的时候可以不加函数参数
* 注意下载文件时(后端不能按照 '{ code: 0, data: {} }' 格式返回),所以请求成功后,必须将下载操作放到error函数中,并从返回的第二个参数获取内容
* */

const httpRequest = {
  /*
  * 使用原生ajax获取换肤的样式文件。
  * 不使用axios是因为 在开发环境下会默认加上 /api/ 前缀，导致反向代理到了开发环境设备，从而获取不到本地静态资源
  */

  getThemeFile() {
    return new Promise((resolve) => {
      const url = `${location.origin}/static/styles/theme.css`;
      const xhr = new XMLHttpRequest();
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4 && xhr.status === 200) {
          resolve(xhr.responseText);
        }
      };
      xhr.open('GET', url);
      xhr.send(null);
    });
  },

  /**-----登陆------strat */
  // 登录模块：登录
  login(query, success, error) {
    requestFn({ method: 'post', url: '/login/user-login', query, success, error });
  },
  //模拟登录请求获取token信息
  testLogin(query, sucess, error) {
    return new Promise(
      (resolve) => {
        if (query.userName == 'admin' && query.password == '123456') {
          setTimeout(() => {
            resolve({
              "code": 200,
              "message": "操作成功",
              "data": {
                token: '123456',
                userName: 'zengyujie',
              }
            })
          }, 100);
        } else {
          setTimeout(() => {
            resolve({
               "code": 400,
              "message": "操作失败,用户名或密码错误",
              "data": {
              }
            })
          }, 100);
        }
      }
    )
  },
  //根据token获取用户信息,这里先写死,无论传什么进来都返回成功的信息,用来测试route,模拟异步请求
  async getUserByToken(query, success, error) {
    return new Promise(resolve => {
      setTimeout(() => {
        const result = {
          "code": 200,
          "message": "操作成功",
          "data": {
            "userId": 1,
            "userName": "secadmin",
            "userPassword": null,
            "originalPassword": null,
            "realName": "系统管理员",
            "email": null,
            "mobilePhoneNumber": null,
            "departmentUuid": null,
            "loginSuccessTimes": 151,
            "lastLoginIp": "1",
            "loginSuccessTime": 1750964525203,
            "lastLoginTime": 1750699406368,
            "loginFailTime": 1750619065846,
            "continuityLoginFailTimes": 0,
            "isLoginFailLocked": 1,
            "isAllowLogin": 0,
            "isOffOnline": 1,
            "userCreateTime": 1736836720310,
            "lastPasswordModifiedTime": 1744817664027,
            "qq": null,
            "description": null,
            "unlockFailTime": null,
            "continuityUnlockFailTimes": 0,
            "isPhoneNumberLogin": 0,
            "userUpdateTime": 1744817664027,
            "createUserVoId": null,
            "updateUserVoId": null,
            "deleted": 0,
            "userPopedomList": null,
            "popedomIds": null,
            "userPopedoms": [
              {
                "moduleGroupName": "登录模块",
                "requestMapping": "login",
                "description": "登录模块",
                "moduleList": [
                  {
                    "moduleName": "登录",
                    "requestMapping": "login",
                    "isEnable": 1,
                    "description": "用户登录"
                  },
                ]
              },
              {
                "moduleGroupName": "用户管理",
                "requestMapping": "userManage",
                "description": "用户管理模块",
                "moduleList": null
              },
              {
                "moduleGroupName": "系统管理",
                "requestMapping": "settings",
                "description": "系统设置",
              },
              {
                "moduleGroupName": "监控墙",
                "requestMapping": "dashboard",
                "description": "监控墙",
              },
              {
                "moduleGroupName": "关于我",
                "requestMapping": "aboutPage",
                "description": "关于我",
                "moduleList":[
                  {
                    "moduleName": "关于我1",
                    "requestMapping": "about1",
                    "description": "关于我1",
                  },
                  {
                    "moduleName": "关于我2",
                    "requestMapping": "about2",
                    "description": "关于我2",
                  }
                ]
              },

            ],
            "departId": 1,
            "departName": "默认组织"
          }
        }
        resolve(result)
      }, 100)
    })
  }

}

export { httpRequest } 
